package com.yyfax.pay.third.facade.fuioudeposit;

import java.io.File;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.yyfax.commons.http.HttpUtil;
import com.yyfax.commons.json.JacksonUtil;
import com.yyfax.commons.lang.DateUtil;
import com.yyfax.framework.common.Log;
import com.yyfax.framework.common.OpResponse;
import com.yyfax.framework.yyfax.CryptUtil;
import com.yyfax.framework.yyfax.enums.TerminalEnum;
import com.yyfax.framework.yyfax.enums.UserTypeEnum;
import com.yyfax.pay.common.constants.FuiouDepositConstant;
import com.yyfax.pay.common.enums.FuiouDepositProtocolSignStateEnum;
import com.yyfax.pay.common.enums.FuiouDepositRspCodeEnum;
import com.yyfax.pay.common.enums.FuiouDepositWithdrawCashWay;
import com.yyfax.pay.common.enums.FuiouDepositWithdrawType;
import com.yyfax.pay.common.enums.FuiouSubjectTypeEnum;
import com.yyfax.pay.common.enums.PaymentEnum;
import com.yyfax.pay.common.exception.PayException;
import com.yyfax.pay.common.exception.PayExceptionCode;
import com.yyfax.pay.common.model.vo.PayRspCodeVO;
import com.yyfax.pay.common.model.vo.RedirectFormVO;
import com.yyfax.pay.common.util.AliasUtil;
import com.yyfax.pay.config.data.service.BankConfigExtService;
import com.yyfax.pay.config.model.po.BankConfigExtPO;
import com.yyfax.pay.depository.domain.FuiouArtificialRegister;
import com.yyfax.pay.depository.domain.FuiouChangeCard;
import com.yyfax.pay.depository.domain.FuiouChangePhone;
import com.yyfax.pay.depository.domain.FuiouCharge;
import com.yyfax.pay.depository.domain.FuiouUserProtocol;
import com.yyfax.pay.depository.domain.FuiouUserRegister;
import com.yyfax.pay.depository.domain.FuiouWithdraw;
import com.yyfax.pay.depository.domain.FuiouWithdrawModifyCash;
import com.yyfax.pay.depository.vo.FuiouResponseVO;
import com.yyfax.pay.depository.vo.QueryChargeWithdrawVO;
import com.yyfax.pay.order.data.service.UserAuthService;
import com.yyfax.pay.order.model.po.UserAuthPO;
import com.yyfax.pay.third.enums.fuioudeposit.WithdrawBusiTypeEnum;
import com.yyfax.pay.third.enums.fuioudeposit.WithdrawCashWayEnum;
import com.yyfax.pay.third.facade.file.FileServerService;
import com.yyfax.pay.third.model.to.file.UploadReq;
import com.yyfax.pay.third.model.to.fuioudeposit.ArtificialRegisterReq;
import com.yyfax.pay.third.model.to.fuioudeposit.ChangeCardSingleReq;
import com.yyfax.pay.third.model.to.fuioudeposit.FuiouChangePhoneFormVO;
import com.yyfax.pay.third.model.to.fuioudeposit.FuiouDepositConfigExt;
import com.yyfax.pay.third.model.to.fuioudeposit.FuiouEbankFormVO;
import com.yyfax.pay.third.model.to.fuioudeposit.FuiouQueryUserInfoReq;
import com.yyfax.pay.third.model.to.fuioudeposit.FuiouTransferChargeFormVO;
import com.yyfax.pay.third.model.to.fuioudeposit.FuiouWithdrawModifyTypeFormVO;
import com.yyfax.pay.third.model.to.fuioudeposit.ProtocolSignQueryReq;
import com.yyfax.pay.third.model.to.fuioudeposit.ProtocolSignRechargeReq;
import com.yyfax.pay.third.model.to.fuioudeposit.ProtocolSignSmsReq;
import com.yyfax.pay.third.model.to.fuioudeposit.ProtocolSigningReq;
import com.yyfax.pay.third.model.to.fuioudeposit.UserRegisterReq;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.BasePlain;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.ChargeWithdrawPlain;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.ChargeWithdrawPlain.ChargeWithdrawResult;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.Plain;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.ProtocalSignQueryPlain;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.ProtocalSignQueryPlain.ProtocalSignQueryItemPlain;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.ProtocalSignSmsPlain;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.RootXMLRsp;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.TransferChargePlain;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.UserInfoPlain;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.UserInfoPlain.UserInfoResult;
import com.yyfax.pay.vo.depository.QueryUserProtocolSignReq;
import com.yyfax.pay.vo.depository.QueryUserProtocolSignRsp;
import com.yyfax.pay.vo.depository.TradeType;

/**
 * 富友存管接口服务
 *
 * @author buyi
 * @date 2017-08-30 09:54:27
 * @since v2.0.0
 */
@Service
public class FuiouDepositService {

	private static final int CONNECT_TIMEOUT = 30000;
	private static final int READ_TIMEOUT = 30000;

	private Logger logger = LoggerFactory.getLogger(getClass());

	/** 富友存管日期格式 */
	private static final String DATE_FORMAT = "yyyyMMdd";

	@Resource
	private FuiouDepositConstant fuiouDepositConstant;

	@Resource
	private FuiouSignService fuiouSignService;

	@Resource
	private BankConfigExtService bankConfigExtService;

	@Resource
	private FuiouErrorCodeService fuiouErrorCodeService;

	@Resource
	private FileServerService fileServerService;

	@Resource
	private UserAuthService userAuthService;

	TypeReference<Map<String, String>> strKV = new TypeReference<Map<String, String>>() {
	};

	private RestTemplate restTemplate;
	private RedirectFormVO<FuiouEbankFormVO> instance;

	/**
	 * 初始化restTemplate，设置超时时间
	 */
	@PostConstruct
	public void initRestTemplate() {
		SimpleClientHttpRequestFactory clientHttpRequestFactory = new MySimpleClientHttpRequestFactory(new NullHostnameVerifier());
		clientHttpRequestFactory.setConnectTimeout(CONNECT_TIMEOUT);
		clientHttpRequestFactory.setReadTimeout(READ_TIMEOUT);

		RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);

		List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
		for (HttpMessageConverter<?> converter : messageConverters) {
			if (converter instanceof AllEncompassingFormHttpMessageConverter) {
				((AllEncompassingFormHttpMessageConverter) converter).setMultipartCharset(FormHttpMessageConverter.DEFAULT_CHARSET);
			}
			if (converter instanceof StringHttpMessageConverter) {
				((StringHttpMessageConverter) converter).setDefaultCharset(FormHttpMessageConverter.DEFAULT_CHARSET);
			}
		}
		this.restTemplate = restTemplate;
	}

	/**
	 * 获取富友网银充值提跳转表单
	 *
	 * @param po ''
	 * @return
	 * @throws Exception ''
	 * @author buyi
	 * @date 2017-08-30 11:21:53
	 * @since v2.0.0
	 */
	public RedirectFormVO<FuiouEbankFormVO> getEbankForm(FuiouCharge po) throws Exception {
		final String op = "FuiouDepositService.getEbankForm";
		FuiouDepositConfigExt depositConfig = fuiouSignService.getFuiouDepositConfig(po.getMerchantCode());

		FuiouEbankFormVO fuiouEbankFormVO = new FuiouEbankFormVO();
		fuiouEbankFormVO.setMerchantCode(depositConfig.getMerchantId());
		fuiouEbankFormVO.setBusiId(po.getBusiId());
		fuiouEbankFormVO.setLoginId(po.getLoginId());
		fuiouEbankFormVO.setAmount(po.getAmount());
		fuiouEbankFormVO.setSubjectType(FuiouSubjectTypeEnum.parse(UserTypeEnum.fromValue(po.getSubjectType())).name());

		// 转换银行编号
		BankConfigExtPO bankconfigExt = bankConfigExtService.queryByThirdBankCode(po.getBankCode(), PaymentEnum.FUIOU_DEPOSIT.getValue());
		if (bankconfigExt == null) {
			logger.error(Log.op(op).msg("富友网银充值 | 封装网银充值表单，银行编码转换成富友银行编码异常").kv("bankCode", po.getBankCode()).kv("payment", PaymentEnum.FUIOU_DEPOSIT)
					.toString());
			throw PayExceptionCode.ERROR_ILL_CONFIG_BANK_EXT.exp();
		}

		fuiouEbankFormVO.setBankCode(bankconfigExt.getBankCodeExt());
		fuiouEbankFormVO.setBackUrl(fuiouDepositConstant.getEbankChargeBackUrl(po.getMerchantCode()));
		fuiouEbankFormVO.setNotifyUrl(fuiouDepositConstant.getEbankChargeNotifyUrl(po.getMerchantCode()));
		fuiouEbankFormVO.setSign(fuiouSignService.getSign(po.getMerchantCode(), fuiouEbankFormVO));

		logger.info(Log.op("FuiouDepositService.getEbankForm").msg("富友存管  网银支付").kv("formAction", depositConfig.getEbankChargeUrl())
				.kv("formBody", fuiouEbankFormVO).toString());

		return RedirectFormVO.newInstance(depositConfig.getEbankChargeUrl(), fuiouEbankFormVO);
	}

	/**
	 * 校验网银充值同步返回结果合法性
	 *
	 * @param merchantCode
	 * @param req
	 * @return false表示校验不通过；true表示校验通过
	 * @author buyi
	 * @date 2017-09-02 11:14:06
	 * @since v2.0.0
	 */
	public boolean checkSignRspResult(String merchantCode, Object req, String signatrue) {
		return fuiouSignService.verifySign(merchantCode, AliasUtil.mergeObjectValues(req, "|"), signatrue);
	}

	/**
	 * 获取更换手机表单参数
	 *
	 * @param po
	 * @return
	 * @author buyi
	 * @date 2017-09-09 11:34:59
	 * @since v2.0.0
	 */
	public RedirectFormVO<FuiouChangePhoneFormVO> getChangePhoneForm(FuiouChangePhone po) {
		FuiouDepositConfigExt depositConfig = fuiouSignService.getFuiouDepositConfig(po.getMerchantCode());
		String url = depositConfig.getChangePhoneUrl();
		if (TerminalEnum.H5.isValue(po.getTerminal()) || TerminalEnum.IOS.isValue(po.getTerminal())
				|| TerminalEnum.ANDROID.isValue(po.getTerminal())) {
			// 如果终端类型为H5，APP
			url = depositConfig.getChangePhoneMobileUrl();
		}

		FuiouChangePhoneFormVO phoneFormVO = new FuiouChangePhoneFormVO();
		phoneFormVO.setMerchantCode(depositConfig.getMerchantId());
		phoneFormVO.setBusiId(po.getBusiId());
		phoneFormVO.setLoginId(po.getLoginId());
		phoneFormVO.setPageBackUrl(fuiouDepositConstant.getChangePhonePageBackUrl(po.getMerchantCode()));
		phoneFormVO.setSign(fuiouSignService.getSign(po.getMerchantCode(), phoneFormVO));

		logger.info(Log.op("FuiouDepositService.getChangePhoneForm").msg("富友存管网关更换手机form请求参数").kv("form", phoneFormVO).toString());

		return RedirectFormVO.newInstance(url, phoneFormVO);
	}

	/**
	 * 发送申请换卡的请求，和其他请求区别在于content-type是multipart/form-data，因为需要发送图片
	 *
	 * @param changeCardEntity
	 * @param url
	 * @param merchantCode
	 * @return
	 * @author yejc
	 * @date 2017-09-20
	 * @since v2.0.0
	 */
	public FuiouResponseVO sendChangeCardRequest(HttpEntity<MultiValueMap<String, Object>> changeCardEntity, String url, String merchantCode) {
		final String op = "FuiouDepositService.sendChangeCardRequest";
		logger.info(Log.op(op).msg("【富友存管】发起HTTP请求").kv("url", url).kv("params", changeCardEntity).toString());
		try {
			String result = restTemplate.postForObject(url, changeCardEntity, String.class);
			logger.info(Log.op(op).msg("【富友存管】接收HTTP响应").kv("result", result).toString());
			if (StringUtils.isBlank(result)) {
				logger.error(Log.op(op).msg("【富友存管】存管接口返回数据异常!").kv("result", result).kv("params", changeCardEntity).toString());
				return new FuiouResponseVO(FuiouResponseVO.INVALID_DATA, result);
			}

			String plain = result.split("<plain>")[1].split("</plain>")[0];
			String signature = result.split("<signature>")[1].split("</signature>")[0];
			boolean verify = fuiouSignService.verifySign(merchantCode, "<plain>" + plain + "</plain>", signature);
			if (!verify) {
				logger.error(Log.op(op).msg("【富友存管】存管接口返回参数验签错误!").kv("result", result).kv("params", changeCardEntity).toString());
				return new FuiouResponseVO(FuiouResponseVO.INVALID_DATA, result);
			}

			return new FuiouResponseVO(FuiouResponseVO.NORMAL, result);
		} catch (RestClientException e) {
			logger.error(Log.op(op).msg("【富友存管】存管接口请求超时!").kv("url", url).kv("params", changeCardEntity).toString(), e);
			return new FuiouResponseVO(FuiouResponseVO.TIMEOUT, null);
		}
	}

	/**
	 * 查询单笔充值订单信息
	 *
	 * @param po
	 * @author buyi
	 * @date 2017-09-25 14:50:17
	 * @since v2.0.0
	 */
	@SuppressWarnings("unchecked")
	public PayRspCodeVO chargeQuery(FuiouCharge po) {
		final String op = "FuiouDepositService.chargeQuery";
		FuiouDepositConfigExt extInfo = fuiouSignService.getFuiouDepositConfig(po.getMerchantCode());
		QueryChargeWithdrawVO queryChargeWithdrawVO = buildChargeOrWithdrawReqParams(TradeType.CHARGE, po.getBusiId(), po.getOrderTime(),
				po.getLoginId(), po.getMerchantCode(), extInfo, null, null);

		FuiouResponseVO resp = postForResponseVO(queryChargeWithdrawVO, extInfo.getQueryChargeWithdrawUrl(), po.getMerchantCode());

		if (resp.getResult().equals(FuiouResponseVO.TIMEOUT)) {
			// 如果请求超时
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_REQ_TIME_OUT);
		}

		if (!resp.getResult().equals(FuiouResponseVO.NORMAL)) {
			// 如果查询异常，则直接抛出
			logger.info(Log.op(op).msg("充值查询，请求富友存管响应异常，未知").kv("busiId", po.getBusiId()).toString());
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN);
		}

		// 神奇的富友充值提现查询接口不仅业务耦合，而且还把单次和批量耦合一起
		RootXMLRsp<ChargeWithdrawPlain> rsp = (RootXMLRsp<ChargeWithdrawPlain>) RootXMLRsp.fromXml(resp.getContent(), ChargeWithdrawPlain.class);

		List<ChargeWithdrawResult> results = rsp.getPlain().getResults();
		if (results == null || results.isEmpty()) {
			// 如果响应为空
			logger.warn(Log.op(op).msg("充值查询，请求富友存管响应数据集为空，可能为订单不存在").kv("busiId", po.getBusiId()).toString());
			return PayRspCodeVO.newInstance(PayExceptionCode.ORDER_NOT_FOUND);
		}

		ChargeWithdrawResult chargeWithdrawResult = results.get(0);

		// 转换响应码，并返回
		// return converToPayRspCode(chargeWithdrawResult.getTxn_rsp_cd());
		return converToPayRspCode(chargeWithdrawResult.getTxn_rsp_cd(), chargeWithdrawResult.getRsp_cd_desc());
	}

	/**
	 * 查询单笔提现订单信息
	 *
	 * @param po
	 * @author yejc
	 * @date 2017-10-09
	 * @since v2.0.0
	 */
	@SuppressWarnings("unchecked")
	public PayRspCodeVO withdrawQuery(FuiouWithdraw po) {
		FuiouDepositConfigExt extInfo = fuiouSignService.getFuiouDepositConfig(po.getMerchantCode());
		QueryChargeWithdrawVO queryChargeWithdrawVO = buildChargeOrWithdrawReqParams(TradeType.WITHDRAW, po.getBusiId(), po.getOrderTime(),
				po.getLoginId(), po.getMerchantCode(), extInfo, null, null);

		FuiouResponseVO resp = postForResponseVO(queryChargeWithdrawVO, extInfo.getQueryChargeWithdrawUrl(), po.getMerchantCode());

		if (resp.getResult().equals(FuiouResponseVO.TIMEOUT)) {
			// 如果请求超时
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_REQ_TIME_OUT);
		}

		if (!resp.getResult().equals(FuiouResponseVO.NORMAL)) {
			// 如果查询异常，则直接抛出
			throw PayExceptionCode.QUERY_ERROR.exp();
		}

		// 神奇的富友充值提现查询接口不仅业务耦合，而且还把单次和批量耦合一起
		RootXMLRsp<ChargeWithdrawPlain> rsp = (RootXMLRsp<ChargeWithdrawPlain>) RootXMLRsp.fromXml(resp.getContent(), ChargeWithdrawPlain.class);

		List<ChargeWithdrawResult> results = rsp.getPlain().getResults();
		if (results == null || results.isEmpty()) {
			// 如果响应为空
			return PayRspCodeVO.newInstance(PayExceptionCode.ORDER_NOT_FOUND);
		}

		ChargeWithdrawResult chargeWithdrawResult = results.get(0);

		// 转换响应码，并返回
		return converToPayRspCode(chargeWithdrawResult.getTxn_rsp_cd());
	}

	/**
	 * 封装充值提现请求参数
	 *
	 * @param tradeType 交易类型
	 * @param busiNo 业务流水号
	 * @param orderTime 订单时间
	 * @param loginId 登录号
	 * @param merchantCode 商户号
	 * @param extInfo 支付凭证信息
	 * @param pageSize 每页记录数
	 * @param pageNo 页数
	 * @return 返回富友充值提现查询请求参数
	 * @author buyi
	 * @date 2017-09-25 11:34:45
	 * @since v2.0.0
	 */
	private QueryChargeWithdrawVO buildChargeOrWithdrawReqParams(TradeType tradeType, String busiNo, String orderTime, String loginId,
			String merchantCode, FuiouDepositConfigExt extInfo, Integer pageSize, Integer pageNo) {
		QueryChargeWithdrawVO vo = new QueryChargeWithdrawVO();
		vo.setVersion(extInfo.getVersion());
		vo.setMerchantId(extInfo.getMerchantId());
		vo.setQueryBusiId(String.valueOf(System.nanoTime()));

		vo.setBusiId(busiNo);
		vo.setType(tradeType.getCode());

		String startTime = DateUtil.datetime2Str(fuiouDepositConstant.getTradeQueryStartDay(DateUtil.str2DateTime(orderTime)));
		vo.setStartTime(startTime);

		String endTime = DateUtil.datetime2Str(fuiouDepositConstant.getTradeQueryEndDay(DateUtil.str2DateTime(orderTime)));
		vo.setEndTime(endTime);

		vo.setAccountNo(loginId);
		vo.setState("");// 交易类型默认为空
		vo.setPageNo(pageNo == null ? "" : String.valueOf(pageNo));
		vo.setPageSize(pageSize == null ? "" : String.valueOf(pageSize));
		vo.setSignature(fuiouSignService.getSign(merchantCode, vo));

		return vo;
	}

	/**
	 * 存管-发送请求, 并封装各种返回
	 *
	 * @param vo
	 * @param url
	 * @return
	 * @author yejc
	 * @date 2017-09-04
	 * @since v2.0.0
	 */
	public FuiouResponseVO postForResponseVO(Object vo, String url, String merchantCode) {
		final String op = "FuiouDepositService.postForResponseVO";
		String jsonString = JSON.toJSONString(vo);
		Map<String, String> params = JSON.parseObject(jsonString, strKV);
		logger.info(Log.op(op).msg("【富友存管】发起HTTP请求").kv("url", url).kv("params", jsonString).toString());
		try {

			// 新增超时时间配置,旧逻辑有问题，超时参数应该作为传参，暂时先这样实现，edit by buyi since 2.0.7
			FuiouDepositConfigExt depositConfig = fuiouSignService.getFuiouDepositConfig(merchantCode);
			int connectTimeout = depositConfig.getConnectTimeout() <= 0 ? CONNECT_TIMEOUT : depositConfig.getConnectTimeout();
			int readTimeout = depositConfig.getReadTimeout() <= 0 ? READ_TIMEOUT : depositConfig.getReadTimeout();

			String result = HttpUtil.postFormDirect(url, params, null, null, null, null, null, connectTimeout, readTimeout, true);
			logger.info(Log.op(op).msg("【富友存管】接收HTTP响应").kv("result", result).toString());
			if (StringUtils.isBlank(result)) {
				logger.error(Log.op(op).msg("【富友存管】存管接口返回数据异常!").kv("result", result).kv("params", jsonString).toString());
				return new FuiouResponseVO(FuiouResponseVO.INVALID_DATA, result);
			}

			String plain = result.split("<plain>")[1].split("</plain>")[0];
			String signature = result.split("<signature>")[1].split("</signature>")[0];
			boolean verify = fuiouSignService.verifySign(merchantCode, "<plain>" + plain + "</plain>", signature);
			if (!verify) {
				logger.error(Log.op(op).msg("【富友存管】存管接口返回参数验签错误!").kv("result", result).kv("params", jsonString).toString());
				return new FuiouResponseVO(FuiouResponseVO.INVALID_DATA, result);
			}

			return new FuiouResponseVO(FuiouResponseVO.NORMAL, result);
		} catch (SocketTimeoutException e) {
			logger.error(Log.op(op).msg("【富友存管】存管接口请求超时!").kv("url", url).kv("params", jsonString).toString(), e);
			return new FuiouResponseVO(FuiouResponseVO.TIMEOUT, null);
		} catch (IOException e) {
			logger.error(Log.op(op).msg("【富友存管】存管接口未知异常!").kv("url", url).kv("params", jsonString).toString(), e);
			return new FuiouResponseVO(FuiouResponseVO.TIMEOUT, null);
		}
	}

	/**
	 * 转换富友响应码为支付响应码,并判断超时、验签失败等情况
	 *
	 * @param resp
	 * @return
	 * @author yejc
	 * @date 2017-10-09
	 * @since v2.0.0
	 */
	public PayRspCodeVO parseResponse(FuiouResponseVO resp) {
		final String op = "FuiouDepositService.parseResponse";

		if (FuiouResponseVO.TIMEOUT.equals(resp.getResult())) {
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_REQ_TIME_OUT.getCode(), PayExceptionCode.WARN_REQ_TIME_OUT.getDesc());
		}

		if (FuiouResponseVO.INVALID_DATA.equals(resp.getResult())) {
			// 其他异常返回对应匹配后的响应消息
			// return PayRspCodeVO.newInstance(PayExceptionCode.SYS_ERROR.getCode(), PayExceptionCode.SYS_ERROR.getDesc());
			// 如果是验签失败，返回未知异常情况,需要后期确认
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), PayExceptionCode.WARN_UNKNOWN.getDesc());

		}

		String result = resp.getContent();
		RootXMLRsp<BasePlain> fuiouRsp = (RootXMLRsp<BasePlain>) RootXMLRsp.fromXml(result, BasePlain.class);
		if (!FuiouDepositRspCodeEnum.SUCCESS.isValue(fuiouRsp.getPlain().getRspCode())) {
			// 不是成功的请求，即富友返回不是0000，打印一下响应信息，方便debug
			logger.warn(Log.op(op).msg("【富友存管】存管接口返回失败").kv("响应码", fuiouRsp.getPlain().getRspCode()).kv("响应消息", fuiouRsp.getPlain().getRspMsg())
					.kv("busiId", fuiouRsp.getPlain().getBusiId()).toString());
		}

		// return this.converToPayRspCode(fuiouRsp.getPlain().getRspCode());
		return this.converToPayRspCode(fuiouRsp.getPlain().getRspCode(), fuiouRsp.getPlain().getRspMsg());
	}

	/**
	 * 转换富友响应码为支付响应码
	 *
	 * @param code
	 * @return
	 * @author buyi
	 * @date 2017-09-27 11:25:21
	 * @since v2.0.0
	 * @deprecated 作废，使用{@linkplain com.yyfax.pay.third.facade.fuioudeposit.FuiouDepositService#converToPayRspCode(String, String)} since 2.2.2
	 */
	public PayRspCodeVO converToPayRspCode(String code) {
		final String op = "FuiouDepositService.converToPayRspCode";
		if (StringUtils.isBlank(code)) {
			logger.warn(Log.op(op).msg("转换富友响应码为支付模块响应码，code响应码为空").toString());
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), "未知异常，响应码为空");
		}

		if (FuiouDepositRspCodeEnum.SUCCESS.isValue(code)) {
			// 请求成功转换
			return PayRspCodeVO.newInstance(PayExceptionCode.SUCCESS.getCode(), PayExceptionCode.SUCCESS.getDesc());
		}

		// 支付响应信息
		String desc = "";

		// 查询富友响应码对应的文案
		String fuiouResp = "";
		try {
			fuiouResp = fuiouErrorCodeService.getProperty(code);
		} catch (Exception e) {
			logger.error(Log.op(op).msg("查询富友响应码对应的提示信息异常").kv("fuiouRespCode", code).toString());
		}

		if (FuiouDepositRspCodeEnum.isTimeout(code)) {
			// 请求超时转换
			desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.WARN_REQ_TIME_OUT.getDesc() : fuiouResp;
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_REQ_TIME_OUT.getCode(), desc);
		}

		if (FuiouDepositRspCodeEnum.isToConfirm(code)) {
			// 请求状态未知，响应码转换为待确认
			desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.TO_CONFIRM.getDesc() : fuiouResp;
			return PayRspCodeVO.newInstance(PayExceptionCode.TO_CONFIRM.getCode(), desc);
		}

		// 不是成功、超时、待确认状态都认为是失败
		// 小黑哥说要这么转换，为了调用方的文案展示
		// if (StringUtils.isBlank(fuiouResp)) {
		// return PayRspCodeVO.newInstance(PayExceptionCode.ERROR_PAY_FAIL.getCode(), PayExceptionCode.ERROR_PAY_FAIL.getDesc());
		// }

		desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.FAIL_TIP_MSG.getDesc() : fuiouResp;
		return PayRspCodeVO.newInstance(PayExceptionCode.FAIL_TIP_MSG.getCode()).setDesc(desc);
	}

	/**
	 * 转换富友响应码为支付响应码
	 * 
	 * @author buyi
	 * @date 2018-06-25 11:43:34
	 * @since v2.2.2
	 * @param code
	 * @param desc
	 * @return
	 */
	public PayRspCodeVO converToPayRspCode(String code, String desc) {
		final String op = "FuiouDepositService.converToPayRspCode";
		if (StringUtils.isBlank(code)) {
			logger.warn(Log.op(op).msg("转换富友响应码为支付模块响应码，code响应码为空").toString());
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), "未知异常，响应码为空");
		}

		if (FuiouDepositRspCodeEnum.SUCCESS.isValue(code)) {
			// 请求成功转换
			return PayRspCodeVO.newInstance(PayExceptionCode.SUCCESS.getCode(), PayExceptionCode.SUCCESS.getDesc());
		}

		// 查询富友响应码对应的文案
		String fuiouResp = "";
		try {
			fuiouResp = fuiouErrorCodeService.getProperty(code, desc);
		} catch (Exception e) {
			logger.error(Log.op(op).msg("查询富友响应码对应的提示信息异常").kv("fuiouRespCode", code).toString());
		}

		if (FuiouDepositRspCodeEnum.isTimeout(code)) {
			// 请求超时转换
			desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.WARN_REQ_TIME_OUT.getDesc() : fuiouResp;
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_REQ_TIME_OUT.getCode(), desc);
		}

		if (FuiouDepositRspCodeEnum.isToConfirm(code)) {
			// 请求状态未知，响应码转换为待确认
			desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.TO_CONFIRM.getDesc() : fuiouResp;
			return PayRspCodeVO.newInstance(PayExceptionCode.TO_CONFIRM.getCode(), desc);
		}

		if (FuiouDepositRspCodeEnum.VALID_CODE_ERROR.isValue(code)) {
			// 验证码错误
			desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.VERIFY_CODE_INVALID.getDesc() : fuiouResp;
			return PayRspCodeVO.newInstance(PayExceptionCode.VERIFY_CODE_INVALID.getCode(), desc);
		}

		if (FuiouDepositRspCodeEnum.DUPLICATE_SEND_SMS.isValue(code)) {
			// 请求获取验证码过频
			desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.ERR0R_DUPLICATE.getDesc() : fuiouResp;
			return PayRspCodeVO.newInstance(PayExceptionCode.ERR0R_DUPLICATE.getCode(), desc);
		}

		if (FuiouDepositRspCodeEnum.SEND_SMS_OVER_LIMIT.isValue(code)) {
			// 该笔交易已失效，超过最大获取验证码次数
			desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.VERIFY_CODE_OVER_LIMIT.getDesc() : fuiouResp;
			return PayRspCodeVO.newInstance(PayExceptionCode.VERIFY_CODE_OVER_LIMIT.getCode(), desc);
		}

		if (FuiouDepositRspCodeEnum.VALID_CODE_CONFIRM_MAX_TIMES_ERROR.isValue(code)) {
			// 表示验证码输入错误的次数超过次数
			desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.VERIFY_CODE_MAX_ERROR.getDesc() : fuiouResp;
			return PayRspCodeVO.newInstance(PayExceptionCode.VERIFY_CODE_MAX_ERROR.getCode(), desc);
		}

		if (FuiouDepositRspCodeEnum.PROTOCOL_DUPILICATE.isValue(code)) {
			// 如果是重复签约，则转换
			desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.PROTOCOL_DUPILICATE.getDesc() : fuiouResp;
			return PayRspCodeVO.newInstance(PayExceptionCode.PROTOCOL_DUPILICATE.getCode(), desc);
		}
		
		if (FuiouDepositRspCodeEnum.INVALID_CARD_ID.isValue(code)) {
			// 无效卡号
			desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.INVALID_CARD_ID.getDesc() : fuiouResp;
			return PayRspCodeVO.newInstance(PayExceptionCode.INVALID_CARD_ID.getCode(), desc);
		}

		if (FuiouDepositRspCodeEnum.UN_SIGN.isValue(code)) {
			// 如果是未签约，则返回
			desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.UN_SIGN.getDesc() : fuiouResp;
			return PayRspCodeVO.newInstance(PayExceptionCode.UN_SIGN.getCode(), desc);
		}
		
		if (FuiouDepositRspCodeEnum.QUERY_OFTEN.isValue(code)) {
			// 如果查询频繁，则转换
			desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.QUERY_OFTEN.getDesc() : fuiouResp;
			return PayRspCodeVO.newInstance(PayExceptionCode.QUERY_OFTEN.getCode(), desc);
		}

		desc = StringUtils.isBlank(fuiouResp) ? PayExceptionCode.FAIL_TIP_MSG.getDesc() : fuiouResp;
		return PayRspCodeVO.newInstance(PayExceptionCode.FAIL_TIP_MSG.getCode()).setDesc(desc);
	}

	/**
	 * 普通用户开户
	 *
	 * @param domain
	 * @return
	 * @author yejc,buyi
	 * @date 2017-10-09
	 * @since v2.0.0
	 */
	public PayRspCodeVO normalUserRegister(FuiouUserRegister po) {
		String op = "FuiouDepositService.normalUserRegister";
		try {
			FuiouDepositConfigExt extInfo = fuiouSignService.getFuiouDepositConfig(po.getMerchantCode());

			// 构建请求参数
			UserRegisterReq userRegisterReq = buildUserRegisterParams(po, extInfo);

			// 请求存管系统
			FuiouResponseVO resp = this.postForResponseVO(userRegisterReq, extInfo.getUserRegisterUrl(), po.getMerchantCode());

			return this.parseResponse(resp);
		} catch (Exception e) {
			logger.error(Log.op(op).msg("【富友存管-个人开户】发送请求失败！").kv("busiId", po.getBusiId()).toString(), e);
			return PayRspCodeVO.newInstance(PayExceptionCode.ERROR_PAY_FAIL.getCode(), PayExceptionCode.ERROR_PAY_FAIL.getDesc());
		}
	}

	/**
	 * 封装个人开户请求参数
	 *
	 * @param domain
	 * @param extInfo
	 * @return
	 * @author yejc
	 * @throws Exception
	 * @date 2017-09-30
	 * @since v2.0.0
	 */
	private UserRegisterReq buildUserRegisterParams(FuiouUserRegister domain, FuiouDepositConfigExt extInfo) throws Exception {
		UserRegisterReq vo = new UserRegisterReq();
		vo.setVersion(extInfo.getVersion());
		vo.setMerchantId(extInfo.getMerchantId());
		vo.setBusiId(domain.getBusiId());
		vo.setCustomerName(domain.getCustomerName());
		vo.setCertificationType(domain.getCertifTp());
		vo.setPhone(domain.getPhone());
		vo.setEmail(domain.getEmail());
		vo.setCityId(domain.getCityCode());

		// vo.setParentBankId(domain.getBankCode());
		BankConfigExtPO bankConfigExtPO = bankConfigExtService.queryByThirdBankCode(domain.getBankCode(), PaymentEnum.FUIOU_DEPOSIT.getValue());
		vo.setParentBankId(bankConfigExtPO.getBankNoExt());

		vo.setBankName(domain.getBankName());
		// 此字段值已无效，后台强制取值同客户姓名
		vo.setAccountName("");
		try {
			vo.setBankCardNo(CryptUtil.decrypt(domain.getCardNoCip()));
			vo.setIdCardNo(CryptUtil.decrypt(domain.getIdNoCip()));
		} catch (Exception e) {
			throw PayExceptionCode.SYS_ERROR.exp();
		}
		vo.setPassword(domain.getPassword());
		vo.setLoginPassword(domain.getLoginPassword());
		vo.setRemark(domain.getRemark());
		vo.setSignature(fuiouSignService.getSign(domain.getMerchantCode(), vo));

		return vo;
	}

	/**
	 * 法人开户
	 *
	 * @param domain
	 * @return
	 * @author yejc
	 * @date 2017-10-09
	 * @since v2.0.0
	 */
	public PayRspCodeVO artificialRegister(FuiouArtificialRegister domain) {
		String op = "FuiouDepositService.normalUserRegister";
		try {
			FuiouDepositConfigExt extInfo = fuiouSignService.getFuiouDepositConfig(domain.getMerchantCode());
			// 构建请求参数
			ArtificialRegisterReq artificialRegisterReq = buildArtificialRegisterParams(domain, extInfo);

			// 请求存管系统
			FuiouResponseVO resp = this.postForResponseVO(artificialRegisterReq, extInfo.getArtificialRegisterUrl(), domain.getMerchantCode());

			return this.parseResponse(resp);
		} catch (Exception e) {
			logger.error(Log.op(op).msg("【富友存管-法人开户】发送请求失败！").kv("busiId", domain.getBusiId()).toString(), e);
			return PayRspCodeVO.newInstance(PayExceptionCode.ERROR_PAY_FAIL.getCode(), PayExceptionCode.ERROR_PAY_FAIL.getDesc());
		}
	}

	/**
	 * 封装法人开户请求参数
	 *
	 * @param domain
	 * @param extInfo
	 * @return
	 * @author yejc
	 * @throws Exception
	 * @date 2017-10-09
	 * @since v2.0.0
	 */
	private ArtificialRegisterReq buildArtificialRegisterParams(FuiouArtificialRegister domain, FuiouDepositConfigExt extInfo) throws Exception {
		ArtificialRegisterReq vo = new ArtificialRegisterReq();
		vo.setVersion(extInfo.getVersion());
		vo.setMerchantId(extInfo.getMerchantId());
		vo.setBusiId(domain.getBusiId());
		vo.setEnterpriseName(domain.getCustomerName());
		vo.setArtificialName(domain.getArtificialName());
		vo.setPhone(domain.getPhone());
		vo.setEmail(domain.getEmail());
		vo.setCityId(domain.getCityCode());

		// vo.setParentBankId(domain.getBankCode());
		BankConfigExtPO bankConfigExtPO = bankConfigExtService.queryByThirdBankCode(domain.getBankCode(), PaymentEnum.FUIOU_DEPOSIT.getValue());
		vo.setParentBankId(bankConfigExtPO.getBankNoExt());

		vo.setBankName(domain.getBankName());
		try {
			vo.setBankCardNo(CryptUtil.decrypt(domain.getCardNoCip()));
			vo.setIdCardNo(CryptUtil.decrypt(domain.getIdNoCip()));
		} catch (Exception e) {
			throw PayExceptionCode.SYS_ERROR.exp();
		}
		vo.setPassword(domain.getPassword());
		vo.setLoginPassword(domain.getLoginPassword());
		vo.setRemark(domain.getRemark());
		vo.setSignature(fuiouSignService.getSign(domain.getMerchantCode(), vo));

		return vo;
	}

	/**
	 * 用户更换银行卡
	 *
	 * @param domain
	 * @return
	 * @author yejc
	 * @date 2017-10-13
	 * @since v2.0.0
	 */
	public PayRspCodeVO userChangeCard(FuiouChangeCard domain) {
		String op = "FuiouDepositService.userChangeCard";
		try {
			FuiouDepositConfigExt extInfo = fuiouSignService.getFuiouDepositConfig(domain.getMerchantCode());
			// 构建请求参数
			HttpEntity<MultiValueMap<String, Object>> changeCardEntity = buildReqParams(domain, extInfo);

			// 请求存管系统
			FuiouResponseVO resp = this.sendChangeCardRequest(changeCardEntity, extInfo.getUserChangeCardUrl(), domain.getMerchantCode());

			return this.parseResponse(resp);
		} catch (Exception e) {
			logger.error(Log.op(op).msg("【富友存管-用户更换银行卡】发送请求失败！").kv("busiId", domain.getBusiId()).toString(), e);
			return PayRspCodeVO.newInstance(PayExceptionCode.ERROR_PAY_FAIL.getCode(), PayExceptionCode.ERROR_PAY_FAIL.getDesc());
		}
	}

	/**
	 * 封装用户更换银行卡请求参数
	 *
	 * @param order
	 * @param extInfo
	 * @return
	 * @author yejc
	 * @date 2017-10-13
	 * @since v2.0.0
	 */
	private HttpEntity<MultiValueMap<String, Object>> buildReqParams(FuiouChangeCard order, FuiouDepositConfigExt extInfo) {
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.MULTIPART_FORM_DATA);

		MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
		String cardNo = null;
		parts.add("mchnt_cd", extInfo.getMerchantId());
		parts.add("mchnt_txn_ssn", order.getBusiId());
		parts.add("login_id", order.getAccountNo());
		parts.add("city_id", order.getCityCode());
		parts.add("bank_cd", order.getBankCode());
		try {
			cardNo = CryptUtil.decrypt(order.getCardNoCip());
			parts.add("card_no", cardNo);
		} catch (Exception e) {
			throw PayExceptionCode.SYS_ERROR.exp();
		}
		FileSystemResource file1 = new FileSystemResource(order.getImageFile1());

		parts.add("file1", file1);
		// 用户没有做过交易，图片2选传
		if (StringUtils.isNotBlank(order.getImageFile2())) {
			FileSystemResource file2 = new FileSystemResource(order.getImageFile2());
			parts.add("file2", file2);
		} else {
			parts.add("file2", "");
		}

		List<String> signVals = Arrays.asList(order.getBankCode(), cardNo, order.getCityCode(), order.getAccountNo(), extInfo.getMerchantId(),
				order.getBusiId());
		String signature = Joiner.on("|").join(signVals);
		parts.add("signature", fuiouSignService.sign(order.getMerchantCode(), signature));
		return new HttpEntity<>(parts, headers);
	}

	/**
	 * 发送修改提现方式请求到富友服务器
	 *
	 * @param po
	 * @return
	 * @author buyi
	 * @date 2017-11-28 16:18:07
	 * @since v2.0.0
	 */
	public PayRspCodeVO doModifyWithdrawCashWay(FuiouWithdrawModifyCash po) {
		final String op = "FuiouDepositService.doModifyWithdrawCashWay";

		try {
			FuiouDepositConfigExt extInfo = fuiouSignService.getFuiouDepositConfig(po.getMerchantCode());
			String url = extInfo.getModifyWithdrawCashWayUrl();
			if (StringUtils.isBlank(url)) {
				throw PayExceptionCode.ERROR_ILL_PARAMETER
						.expMsg("merchantPayment缺少配置,modifyWithdrawCashWayUrl为空，merchantCode:" + po.getMerchantCode());
			}

			// 构建请求参数
			FuiouWithdrawModifyTypeFormVO form = buildModifyWithdrawCashWayFormVO(po, extInfo);

			FuiouResponseVO fuiouResponseVO = postForResponseVO(form, url, po.getMerchantCode());
			// 转换富友响应参数为支付响应参数
			if (FuiouResponseVO.TIMEOUT.equals(fuiouResponseVO.getResult())) {
				return PayRspCodeVO.newInstance(PayExceptionCode.WARN_REQ_TIME_OUT.getCode(), PayExceptionCode.WARN_REQ_TIME_OUT.getDesc());
			}

			if (FuiouResponseVO.INVALID_DATA.equals(fuiouResponseVO.getResult())) {
				// 其他异常返回对应匹配后的响应消息
				return PayRspCodeVO.newInstance(PayExceptionCode.SYS_ERROR.getCode(), PayExceptionCode.SYS_ERROR.getDesc());
			}

			RootXMLRsp<BasePlain> fuiouRsp = (RootXMLRsp<BasePlain>) RootXMLRsp.fromXml(fuiouResponseVO.getContent(), BasePlain.class);

			return converToPayRspCode(fuiouRsp.getPlain().getRspCode());
		} catch (Exception e) {
			logger.error(Log.op(op).msg("修改提现方式出现未知异常").kv("FuiouWithdrawModifyCash", po).toString(), e);

			return PayRspCodeVO.newInstance(PayExceptionCode.SYS_ERROR);
		}
	}

	/**
	 * 构建修改提现方式请求参数
	 *
	 * @param po
	 * @return
	 * @author buyi
	 * @date 2017-11-28 16:30:51
	 * @since v2.0.0
	 */
	private FuiouWithdrawModifyTypeFormVO buildModifyWithdrawCashWayFormVO(FuiouWithdrawModifyCash po, FuiouDepositConfigExt extInfo) {

		WithdrawBusiTypeEnum busiType = WithdrawBusiTypeEnum.parse(FuiouDepositWithdrawType.from(po.getBusiType()));
		WithdrawCashWayEnum cashWay = WithdrawCashWayEnum.parse(FuiouDepositWithdrawCashWay.from(po.getCashWay()));

		FuiouWithdrawModifyTypeFormVO form = FuiouWithdrawModifyTypeFormVO.newInstance().setMchntCd(extInfo.getMerchantId())
				.setMchntTxnSsn(po.getBusiId()).setLoginId(po.getLoginId()).setCashWay(cashWay.getValue()).setBusiTp(busiType.getValue());

		// 设置签名
		form.setSignature(fuiouSignService.getSign(po.getMerchantCode(), form));

		return form;
	}

	/**
	 * 查询用户信息
	 * 
	 * @author buyi
	 * @date 2018-01-16 14:25:37
	 * @since v2.0.7
	 * @param busiId 业务主键
	 * @param merchantCode 商户号
	 * @param phone 手机号
	 * @return
	 */
	public PayRspCodeVO queryUserInfo(String busiId, String merchantCode, String phone) {
		final String op = "FuiouDepositService.queryUserInfo";

		FuiouDepositConfigExt extInfo = fuiouSignService.getFuiouDepositConfig(merchantCode);
		// 构建请求查询用户信息请求参数
		FuiouQueryUserInfoReq req = FuiouQueryUserInfoReq.newInstance().version(extInfo.getVersion()).merchantId(extInfo.getMerchantId())
				.queryBusiId(String.valueOf(System.nanoTime())).tradeDate(DateUtil.date2Str(new Date(), "yyyyMMdd")).accountNos(phone);
		req.signature(fuiouSignService.getSign(merchantCode, req));

		logger.info(Log.op(op).msg("【富友存管用户信息查询】请求参数").kv("busiId", busiId).kv("QueryUserInfoReq", req).toString());
		// 发请求
		FuiouResponseVO resp = this.postForResponseVO(req, extInfo.getQueryUserInfoUrl(), merchantCode);

		logger.info(Log.op(op).msg("【富友存管用户信息查询】请求响应参数").kv("busiId", busiId).kv("FuiouResponseVO", resp).toString());

		PayRspCodeVO payRspCodeVO = parseResponse(resp);
		if (PayExceptionCode.SUCCESS.isCode(payRspCodeVO.getCode())) {
			// 如果请求头为成功，则解析报文中开户状态
			RootXMLRsp<UserInfoPlain> rsp = (RootXMLRsp<UserInfoPlain>) RootXMLRsp.fromXml(resp.getContent(), UserInfoPlain.class);
			UserInfoResult result = rsp.getPlain().getResults().get(0);

			payRspCodeVO.obj(result);
			if (result == null) {
				payRspCodeVO.setCode(PayExceptionCode.ERROR_NOT_OPEN_ACCOUNT.getCode());
				payRspCodeVO.setDesc(PayExceptionCode.ERROR_NOT_OPEN_ACCOUNT.getDesc());
				return payRspCodeVO;
			}

			if (StringUtils.isBlank(result.getUserState())) {
				// 如果响应状态为空，则表示没有开户
				payRspCodeVO.setCode(PayExceptionCode.ERROR_NOT_OPEN_ACCOUNT.getCode());
				payRspCodeVO.setDesc(PayExceptionCode.ERROR_NOT_OPEN_ACCOUNT.getDesc());
				return payRspCodeVO;
			}

			// 用户状态1-正常，2-已注销，3-申请注销
			if (!StringUtils.equals(result.getUserState(), "1")) {
				// 如果用户状态不是正常，则表示失败

				payRspCodeVO.setCode(PayExceptionCode.ERROR_PAY_FAIL.getCode());
				payRspCodeVO.setDesc(PayExceptionCode.ERROR_PAY_FAIL.getDesc());
				return payRspCodeVO;
			}
		}

		return payRspCodeVO;
	}

	/**
	 * 封装富友转账充值网关表单
	 * 
	 * @author buyi
	 * @date 2018-03-28 10:53:31
	 * @since v2.1.3
	 * @param po
	 * @return
	 */
	public RedirectFormVO<FuiouTransferChargeFormVO> getTransferChargeForm(FuiouCharge po) {
		final String op = "FuiouDepositService.getTransferChargeForm";
		FuiouDepositConfigExt depositConfig = fuiouSignService.getFuiouDepositConfig(po.getMerchantCode());

		FuiouTransferChargeFormVO formVO = new FuiouTransferChargeFormVO();
		formVO.setMerchantCode(depositConfig.getMerchantId());
		formVO.setBusiId(po.getBusiId());
		formVO.setLoginId(po.getLoginId());
		formVO.setAmount(po.getAmount());

		formVO.setBackUrl(fuiouDepositConstant.getTransferChargeBackNotifyUrl(po.getMerchantCode()));
		formVO.setSign(fuiouSignService.getSign(po.getMerchantCode(), formVO));

		String url = depositConfig.getTransferChargeUrl();
		if (StringUtils.isBlank(url)) {
			logger.error(
					Log.op(op).msg("富友存管转账充值[merchant_payment]transferChargeUrl配置不正确，请检查数据库配置").kv("merchantCode", po.getMerchantCode()).toString());
			throw PayExceptionCode.ERROR_ILL_PARAMETER.exp();
		}

		logger.info(Log.op(op).msg("富友存管 转账充值").kv("formAction", url).kv("formBody", formVO).toString());

		return RedirectFormVO.newInstance(url, formVO);
	}

	/**
	 * 获取更换银行卡地址
	 * 
	 * @author buyi
	 * @date 2018-03-29 13:56:48
	 * @since v2.1.3
	 * @param idNo
	 * @param imageFilePath 图片文件路径
	 * @param dateStr 日期
	 * @return
	 * @throws Exception
	 */
	public PayRspCodeVO uploadChangeCardImage(FuiouChangeCard po) throws Exception {
		try {
			FuiouDepositConfigExt depositConfig = fuiouSignService.getFuiouDepositConfig(po.getMerchantCode());

			String actImageFilePath = generateChangeCardImage(depositConfig, po);

			PayRspCodeVO vo = doUploadChangeCardImage(depositConfig, actImageFilePath, po);
			vo.obj(actImageFilePath);

			return vo;
		} catch (Exception e) {
			logger.error(Log.op("FuiouDepositService.uploadChangeCardImage").msg("更换银行卡 | 图片上传请求文件服务异常").kv("FuiouChangeCard", po).toString(), e);
			return PayRspCodeVO.newInstance(PayExceptionCode.ERROR_FILE_FAIL);
		}

	}

	/**
	 * 生成更换银行卡图片
	 * 
	 * @author buyi
	 * @date 2018-03-29 16:11:44
	 * @since v2.1.3
	 * @param depositConfig
	 * @param po
	 * @return
	 * @throws Exception
	 */
	private String generateChangeCardImage(FuiouDepositConfigExt depositConfig, FuiouChangeCard po) throws Exception {
		UserAuthPO userAuthPO = userAuthService.queryByUserIdAndMerchantCode(po.getMerchantUserId(), po.getMerchantCode());
		if (userAuthPO == null) {
			throw PayExceptionCode.ERROR_ILL_PARAMETER.expMsg("未开户");
		}

		String idNo = CryptUtil.decrypt(userAuthPO.getIdNoCip());

		String dateStr = DateUtil.date2Str(DateUtil.str2Date(po.getCreateTime()), "yyyyMMdd");

		File imageFile = new File(po.getImageFile1());
		// 图片后缀
		String imageFileSuffix = imageFile.getName().substring(imageFile.getName().lastIndexOf("."));

		// 组装新的图片名称，格式：商户号+身份证号+日期
		String newImageFileName = depositConfig.getMerchantId().concat(idNo).concat(dateStr).concat(imageFileSuffix);

		Path imagePath = Paths.get(depositConfig.getFileStorePrefix(), po.getImageFile1());

		Path newImagePath = Paths.get(depositConfig.getFileStorePrefix(), imageFile.getParent(), newImageFileName);

		// 如果新文件存在，则删除
		Files.deleteIfExists(newImagePath);

		// 复制文件
		Files.copy(imagePath, newImagePath);

		// 重新生成的文件的相对路径
		String actImageFilePath = imageFile.getParent().concat("/").concat(newImageFileName);

		return actImageFilePath;
	}

	/**
	 * 执行文件上传，通过调用文件服务<br>
	 * 
	 * @author buyi
	 * @date 2018-03-29 16:17:24
	 * @since v2.1.3
	 * @param depositConfig
	 * @param actImageFilePath
	 * @throws IOException
	 * @pamam
	 */
	private PayRspCodeVO doUploadChangeCardImage(FuiouDepositConfigExt config, String actImageFilePath, FuiouChangeCard po) throws IOException {
		final String op = "FuiouDepositService.doUploadChangeCardImage";

		String notifyUrl = fuiouDepositConstant.getFileImageUploadNotifyUrl(po.getMerchantCode());
		// 上传文件到文件服务请求参数
		UploadReq req = new UploadReq();

		req.setOrderId(po.getBusiId());
		req.setLocalDirectory(config.getFileServerLocalRootDir());
		req.setRemoteDirectory(config.getImageFtpRemoteRootDir().concat("/")
				.concat(DateUtil.date2Str(DateUtil.str2Date(po.getCreateTime()), "yyyyMMdd")).concat("/"));
		req.setBusinessType(config.getImageUploadBusiType()); // 交易
		req.setNotifyUrl(notifyUrl); // 异步通知地址
		req.setFileNames(Lists.newArrayList(actImageFilePath));
		req.setProtocol(config.getImageUploadProtocol());

		// 脱敏字符串
		String desensitizationBody = JSON.toJSONString(req);

		req.setHost(config.getImageFtpHost());
		req.setPort(config.getImageFtpPort());
		req.setName(config.getImageFtpName());
		req.setPassword(config.getImageFtpPassword());
		String body = JSON.toJSONString(req);

		logger.info(Log.op(op).msg("更换银行卡 | 图片上传请求参数").kv("busiId", po.getBusiId()).kv("uploadUrl", config.getFileServerUploadUrl())
				.kv("desensitizationBody", desensitizationBody).toString());

		String rspStr = null;
		try {
			rspStr = HttpUtil.postBodyDirect(config.getFileServerUploadUrl(), body, null, null, null, null, null,
					config.getFileServerConnectTimeout(), config.getFileServerReadTimeout());

			logger.info(Log.op(op).msg("更换银行卡 | 图片上传请求文件服务响应参数").kv("busiId", po.getBusiId()).kv("rsp", rspStr).toString());

			OpResponse opResponse = JacksonUtil.fromJson(rspStr, OpResponse.class);

			// 转换文件系统响应码
			if (PayExceptionCode.SUCCESS.isCode(opResponse.getCode())) {
				return PayRspCodeVO.newInstance(PayExceptionCode.SUCCESS);
			}

			// 如果不是成功，则默认为失败
			return PayRspCodeVO.newInstance(PayExceptionCode.ERROR_FILE_FAIL);
		} catch (IOException e) {
			logger.error(Log.op(op).msg("更换银行卡 | 图片上传请求文件服务异常").kv("FuiouChangeCard", po).toString(), e);
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_REQ_TIME_OUT);
		}

	}

	/**
	 * 触发单卡验证更换银行卡
	 * 
	 * @author buyi
	 * @date 2018-03-30 09:11:35
	 * @since v2.1.3
	 * @param po
	 * @return
	 */
	public PayRspCodeVO doChangeCardSingle(FuiouChangeCard po) {
		String op = "FuiouDepositService.doChangeCardSingle";
		try {
			FuiouDepositConfigExt config = fuiouSignService.getFuiouDepositConfig(po.getMerchantCode());
			String url = config.getUserChangeCardSingleUrl();

			if (StringUtils.isBlank(url)) {
				logger.error(Log.op(op).msg("【富友存管-用户更换银行卡】卡失败，FuiouDepositConfigExt.UserChangeCardSingleUrl配置为空，请重新检查merchant_payment.ext配置")
						.kv("FuiouChangeCard", po).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.ERROR_PAY_FAIL.getCode(), PayExceptionCode.ERROR_PAY_FAIL.getDesc());
			}

			BankConfigExtPO bankConfigExtPO = bankConfigExtService.queryByThirdBankCode(po.getBankCode(), PaymentEnum.FUIOU_DEPOSIT.getValue());

			ChangeCardSingleReq req = new ChangeCardSingleReq();

			req.setMchnt_cd(config.getMerchantId());
			req.setMchnt_txn_ssn(po.getBusiId());
			req.setLogin_id(po.getAccountNo());
			req.setCard_no(CryptUtil.decrypt(po.getCardNoCip()));
			req.setBank_cd(bankConfigExtPO.getBankNoExt());
			req.setBank_nm(po.getBankName());
			req.setCity_id(po.getCityCode());

			req.setSignature(fuiouSignService.getSign(po.getMerchantCode(), req));

			// Map reqMap = new BeanMap(req);
			// Map<String, String> reqMap = Maps.newHashMap();
			// reqMap.put("mchnt_cd", req.getMchnt_cd());
			// reqMap.put("mchnt_txn_ssn", req.getMchnt_txn_ssn());
			// reqMap.put("login_id", req.getLogin_id());
			// reqMap.put("card_no", req.getCard_no());
			// reqMap.put("bank_cd", req.getBank_cd());
			// reqMap.put("bank_nm", req.getBank_nm());
			// reqMap.put("city_id", req.getCity_id());
			// reqMap.put("signature", req.getSignature());
			Map<String, String> reqMap = AliasUtil.coverToMapAll(req);

			logger.info(Log.op(op).msg("【富友存管-用户更换银行卡】 http请求").kv("url", url).kv("params", reqMap).kv("busiId", po.getBusiId()).toString());

			String result = HttpUtil.postFormDirect(url, reqMap, null, null, null, null, null, config.getConnectTimeout(), config.getReadTimeout());

			logger.info(Log.op(op).msg("【富友存管-用户更换银行卡】 http响应").kv("url", url).kv("params", reqMap).kv("result", result).kv("busiId", po.getBusiId())
					.toString());

			if (StringUtils.isBlank(result)) {
				logger.error(Log.op(op).msg("【富友存管-用户更换银行卡】失败，返回参数为空").kv("url", url).kv("params", reqMap).kv("result", result)
						.kv("busiId", po.getBusiId()).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.SYS_ERROR.getCode(), PayExceptionCode.SYS_ERROR.getDesc());
			}

			String plain = result.split("<plain>")[1].split("</plain>")[0];
			String signature = result.split("<signature>")[1].split("</signature>")[0];
			boolean verify = fuiouSignService.verifySign(po.getMerchantCode(), "<plain>" + plain + "</plain>", signature);
			if (!verify) {
				// 返回校验不通过
				logger.error(Log.op(op).msg("【富友存管-用户更换银行卡】失败，校验请求响应参数,验签不通过").kv("url", url).kv("params", reqMap).kv("result", result)
						.kv("busiId", po.getBusiId()).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.SYS_ERROR.getCode(), PayExceptionCode.SYS_ERROR.getDesc());
			}

			RootXMLRsp<Plain> fuiouRsp = (RootXMLRsp<Plain>) RootXMLRsp.fromXml(result, Plain.class);

			PayRspCodeVO payRspCodeVO = converToPayRspCode(fuiouRsp.getPlain().getRespCode());
			payRspCodeVO.setDesc(fuiouRsp.getPlain().getDescCode());

			return payRspCodeVO;
		} catch (SocketTimeoutException e) {
			logger.error(Log.op(op).msg("【富友存管-用户更换银行卡】请求超时").kv("busiId", po.getBusiId()).toString());
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_REQ_TIME_OUT.getCode(), PayExceptionCode.WARN_REQ_TIME_OUT.getDesc());
		} catch (Exception e) {
			logger.error(Log.op(op).msg("【富友存管-用户更换银行卡】发送请求失败！").kv("busiId", po.getBusiId()).toString(), e);
			return PayRspCodeVO.newInstance(PayExceptionCode.ERROR_PAY_FAIL.getCode(), PayExceptionCode.ERROR_PAY_FAIL.getDesc());
		}

	}

	/**
	 * 请求转账充值(无页面)
	 * 
	 * @author buyi
	 * @date 2018-06-04 16:06:59
	 * @since v1.0.0
	 * @param po
	 * @return
	 */

	public PayRspCodeVO doTransfer(FuiouCharge po) {
		final String op = "FuiouDepositService.doTransfer";
		PayRspCodeVO payRspCodeVO = PayRspCodeVO.newInstance();
		// FuiouDepositDataBinder binder = new FuiouDepositDataBinder();
		// binder.setBuisId(po.getBusiId());
		try {
			FuiouDepositConfigExt depositConfig = fuiouSignService.getFuiouDepositConfig(po.getMerchantCode());

			// 封装请求参数
			FuiouTransferChargeFormVO req = new FuiouTransferChargeFormVO();
			req.setMerchantCode(depositConfig.getMerchantId());
			req.setBusiId(po.getBusiId());
			req.setLoginId(po.getLoginId());
			req.setAmount(po.getAmount());

			req.setBackUrl(fuiouDepositConstant.getTransferChargeBackNotifyUrl(po.getMerchantCode()));
			req.setSign(fuiouSignService.getSign(po.getMerchantCode(), req));

			// 将请求参数转换成map

			JSONObject jsonObject = (JSONObject) JSON.toJSON(req);
			Set<Entry<String, Object>> entrySet = jsonObject.entrySet();
			Map<String, String> reqMap = new HashMap();
			for (Entry<String, Object> entry : entrySet) {
				reqMap.put(entry.getKey(), String.valueOf(entry.getValue()));
			}

			String url = depositConfig.getTransferChargeApiUrl();
			if (StringUtils.isBlank(url)) {
				logger.error(Log.op(op).msg("merchant_payment.ext配置缺失transferChargeApiUrl配置").kv("merchantCode", po.getMerchantCode()).toString());
				throw PayExceptionCode.ERROR_ILL_PARAMETER.exp();
			}

			logger.info(Log.op(op).msg("【富友存管-转账充值无页面】 http请求").kv("url", url).kv("params", reqMap).kv("busiId", po.getBusiId()).toString());

			String result = HttpUtil.postFormDirect(url, reqMap, null, null, null, null, null, depositConfig.getConnectTimeout(),
					depositConfig.getReadTimeout());

			logger.info(Log.op(op).msg("【富友存管-转账充值无页面】 http响应").kv("url", url).kv("params", reqMap).kv("result", result).kv("busiId", po.getBusiId())
					.toString());

			boolean verify = commonVerify(po.getMerchantCode(), result);
			if (!verify) {
				// 如果验签不通过，则返回未知异常
				logger.error(Log.op(op).msg("【富友存管-转账充值无页面】失败，校验请求响应参数,验签不通过").kv("url", url).kv("params", reqMap).kv("result", result)
						.kv("busiId", po.getBusiId()).toString());

				payRspCodeVO.setCode(PayExceptionCode.WARN_UNKNOWN.getCode());
				payRspCodeVO.setDesc(PayExceptionCode.WARN_UNKNOWN.getDesc());
				return payRspCodeVO;
			}

			RootXMLRsp<TransferChargePlain> fuiouRsp = (RootXMLRsp<TransferChargePlain>) RootXMLRsp.fromXml(result, TransferChargePlain.class);
			if (fuiouRsp == null || fuiouRsp.getPlain() == null) {
				// 如果响应报文不规范，则返回未知状态
				logger.error(Log.op(op).msg("【富友存管-转账充值无页面】返回响应报文为空").kv("busiId", po.getBusiId()).toString());

				payRspCodeVO.setCode(PayExceptionCode.WARN_UNKNOWN.getCode());
				payRspCodeVO.setDesc(PayExceptionCode.WARN_UNKNOWN.getDesc());
				return payRspCodeVO;
			}

			payRspCodeVO = converToPayRspCode(fuiouRsp.getPlain().getRespCode(), fuiouRsp.getPlain().getDescCode());

			payRspCodeVO.obj(fuiouRsp.getPlain());

			return payRspCodeVO;
		} catch (PayException e) {
			payRspCodeVO.setCode(e.getCode());
			payRspCodeVO.setDesc(e.getMessage());
		} catch (SocketTimeoutException e) {
			logger.warn(Log.op(op).msg("发起无页面委托充值超时").kv("busiId", po.getBusiId()).toString(), e);

			payRspCodeVO.setCode(PayExceptionCode.WARN_REQ_TIME_OUT.getCode());
			payRspCodeVO.setDesc(PayExceptionCode.WARN_REQ_TIME_OUT.getDesc());
			payRspCodeVO.setTimeout(true);
		} catch (Exception e) {
			logger.error(Log.op(op).msg("发起无页面委托充值未知异常").kv("busiId", po.getBusiId()).toString(), e);
			payRspCodeVO.setCode(PayExceptionCode.WARN_UNKNOWN.getCode());
			payRspCodeVO.setDesc(PayExceptionCode.WARN_UNKNOWN.getDesc());
		}

		return payRspCodeVO;
	}

	/**
	 * 公共验签方法<br>
	 * 
	 * @author buyi
	 * @date 2018-06-04 16:24:50
	 * @since v1.0.0
	 * @param rspText
	 * @return
	 */
	public boolean commonVerify(String merchantCode, String result) {
		boolean isSign = false;
		try {
			String plain = result.split("<plain>")[1].split("</plain>")[0];
			String signature = result.split("<signature>")[1].split("</signature>")[0];
			return fuiouSignService.verifySign(merchantCode, "<plain>" + plain + "</plain>", signature);
		} catch (Exception e) {
			logger.error(Log.op("FuiouDepositService.commonVerify").msg("富友存管请求响应参数验签异常").kv("请求报文", result).toString());
		}
		return isSign;
	}

	/**
	 * 触发协议绑定发送短信验证码接口
	 * 
	 * @author buyi
	 * @date 2018-06-14 14:09:35
	 * @since v1.0.0
	 * @param fuiouUserProtocol
	 * @return
	 */
	public PayRspCodeVO doProtocolSignSms(FuiouUserProtocol fuiouUserProtocol) {
		String op = "FuiouDepositService.doProtocolSignSms";
		try {
			FuiouDepositConfigExt config = fuiouSignService.getFuiouDepositConfig(fuiouUserProtocol.getMerchantCode());
			String url = config.getProtocolSignSmsUrl();

			if (StringUtils.isBlank(url)) {
				logger.error(Log.op(op).msg("【富友存管-协议绑定发送短信验证码接口】失败，FuiouDepositConfigExt.protocolSignSmsUrl配置为空，请重新检查merchant_payment.ext配置")
						.kv("FuiouChangeCard", fuiouUserProtocol).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.ERROR_OPT_FAIL.getCode(), PayExceptionCode.ERROR_OPT_FAIL.getDesc());
			}

			// 封装请求参数
			Map<String, String> reqMap = buildProtocolSignSmsReq(fuiouUserProtocol, config);

			logger.info(Log.op(op).msg("【富友存管-协议绑定发送短信验证码接口】】 http请求").kv("url", url).kv("params", reqMap).kv("busiId", fuiouUserProtocol.getBusiId())
					.toString());

			String result = HttpUtil.postFormDirect(url, reqMap, null, null, null, null, null, config.getConnectTimeout(), config.getReadTimeout());

			logger.info(Log.op(op).msg("【富友存管-协议绑定发送短信验证码接口】】 http响应").kv("url", url).kv("params", reqMap).kv("result", result)
					.kv("busiId", fuiouUserProtocol.getBusiId()).toString());

			if (StringUtils.isBlank(result)) {
				logger.error(Log.op(op).msg("【富友存管-协议绑定发送短信验证码接口】】失败，返回参数为空").kv("url", url).kv("params", reqMap).kv("result", result)
						.kv("busiId", fuiouUserProtocol.getBusiId()).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), PayExceptionCode.WARN_UNKNOWN.getDesc());
			}

			boolean verify = commonVerify(fuiouUserProtocol.getMerchantCode(), result);
			if (!verify) {
				// 返回校验不通过
				logger.error(Log.op(op).msg("【富友存管-协议绑定发送短信验证码接口】】响应参数,验签不通过,返回未知异常").kv("url", url).kv("params", reqMap).kv("result", result)
						.kv("busiId", fuiouUserProtocol.getBusiId()).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), PayExceptionCode.WARN_UNKNOWN.getDesc());
			}

			RootXMLRsp<ProtocalSignSmsPlain> fuiouRsp = (RootXMLRsp<ProtocalSignSmsPlain>) RootXMLRsp.fromXml(result, ProtocalSignSmsPlain.class);

			PayRspCodeVO payRspCodeVO = converToPayRspCode(fuiouRsp.getPlain().getRespCode(), fuiouRsp.getPlain().getRespDesc());
			payRspCodeVO.setObj(fuiouRsp.getPlain());

			return payRspCodeVO;
		} catch (SocketTimeoutException e) {
			logger.error(Log.op(op).msg(op).kv("busiId", fuiouUserProtocol.getBusiId()).toString(), e);
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_REQ_TIME_OUT.getCode(), PayExceptionCode.WARN_REQ_TIME_OUT.getDesc());
		} catch (PayException e) {
			return PayRspCodeVO.newInstance(e.getCode(), e.getMessage());
		} catch (Exception e) {
			logger.error(Log.op(op).msg("【富友存管-协议绑定发送短信验证码接口】】发送请求失败！").kv("busiId", fuiouUserProtocol.getBusiId()).toString(), e);
			return PayRspCodeVO.newInstance(PayExceptionCode.FAIL_TIP_MSG.getCode(), PayExceptionCode.FAIL_TIP_MSG.getDesc());
		}
	}

	/**
	 * 构建协议绑定发送短信请求参数
	 * 
	 * @author buyi
	 * @date 2018-06-19 19:21:30
	 * @since v2.2.2
	 * @param fuiouUserProtocol
	 * @param config
	 * @return
	 * @throws Exception
	 */
	private Map<String, String> buildProtocolSignSmsReq(FuiouUserProtocol fuiouUserProtocol, FuiouDepositConfigExt config) throws Exception {
		ProtocolSignSmsReq req = new ProtocolSignSmsReq();
		req.setMchntCd(config.getMerchantId());
		req.setMchntTxnSsn(fuiouUserProtocol.getBusiId());
		req.setLoginId(fuiouUserProtocol.getLoginId());
		req.setCardNm(fuiouUserProtocol.getName());
		req.setCardNo(CryptUtil.decrypt(fuiouUserProtocol.getCardNoCip()));
		req.setIdNo(CryptUtil.decrypt(fuiouUserProtocol.getIdNoCip()));
		req.setMobile(fuiouUserProtocol.getPhone());
		req.setIndateNum(String.valueOf(fuiouUserProtocol.getIndateNum()));

		// 生成签名串
		String signature = fuiouSignService.getSign(fuiouUserProtocol.getMerchantCode(), req);
		req.setSignature(signature);

		return AliasUtil.coverToMapAll(req);
	}

	/**
	 * 请求协议绑定确认
	 * 
	 * @author buyi
	 * @date 2018-06-15 16:16:04
	 * @since v2.2.2
	 * @param fuiouUserProtocol
	 * @return
	 */
	public PayRspCodeVO doProtocolSigning(FuiouUserProtocol fuiouUserProtocol, String salt, String validCode) {
		String op = "FuiouDepositService.doProtocolSigning";
		try {
			FuiouDepositConfigExt config = fuiouSignService.getFuiouDepositConfig(fuiouUserProtocol.getMerchantCode());
			String url = config.getProtocolSigningUrl();

			if (StringUtils.isBlank(url)) {
				logger.error(Log.op(op).msg("【富友存管-协议绑定确认接口】失败，FuiouDepositConfigExt.protocolSigningUrl配置为空，请重新检查merchant_payment.ext配置")
						.kv("FuiouChangeCard", fuiouUserProtocol).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.ERROR_OPT_FAIL.getCode(), PayExceptionCode.ERROR_OPT_FAIL.getDesc());
			}

			// 封装请求参数
			Map<String, String> reqMap = buildProtocolSigning(fuiouUserProtocol, config, salt, validCode);

			logger.info(Log.op(op).msg("【富友存管-协议绑定确认接口】】 http请求").kv("url", url).kv("params", reqMap).kv("busiId", fuiouUserProtocol.getBusiId())
					.toString());

			String result = HttpUtil.postFormDirect(url, reqMap, null, null, null, null, null, config.getConnectTimeout(), config.getReadTimeout());

			logger.info(Log.op(op).msg("【富友存管-协议绑定确认接口】 http响应").kv("url", url).kv("params", reqMap).kv("result", result)
					.kv("busiId", fuiouUserProtocol.getBusiId()).toString());

			if (StringUtils.isBlank(result)) {
				logger.error(Log.op(op).msg("【富富友存管-协议绑定确认接口】响应参数异常，返回参数为空").kv("url", url).kv("params", reqMap).kv("result", result)
						.kv("busiId", fuiouUserProtocol.getBusiId()).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), PayExceptionCode.WARN_UNKNOWN.getDesc());
			}

			boolean verify = commonVerify(fuiouUserProtocol.getMerchantCode(), result);
			if (!verify) {
				// 返回校验不通过
				logger.error(Log.op(op).msg("【富富友存管-协议绑定确认接口】响应参数异常,验签不通过,返回未知异常").kv("url", url).kv("params", reqMap).kv("result", result)
						.kv("busiId", fuiouUserProtocol.getBusiId()).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), PayExceptionCode.WARN_UNKNOWN.getDesc());
			}

			RootXMLRsp<ProtocalSignSmsPlain> fuiouRsp = (RootXMLRsp<ProtocalSignSmsPlain>) RootXMLRsp.fromXml(result, ProtocalSignSmsPlain.class);

			PayRspCodeVO payRspCodeVO = converToPayRspCode(fuiouRsp.getPlain().getRespCode(), fuiouRsp.getPlain().getRespDesc());
			payRspCodeVO.setObj(fuiouRsp.getPlain());

			return payRspCodeVO;
		} catch (SocketTimeoutException e) {
			logger.error(Log.op(op).msg("【富富友存管-协议绑定确认接口】请求超时").kv("busiId", fuiouUserProtocol.getBusiId()).toString(), e);
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_REQ_TIME_OUT.getCode(), PayExceptionCode.WARN_REQ_TIME_OUT.getDesc());
		} catch (PayException e) {
			return PayRspCodeVO.newInstance(e.getCode(), e.getMessage());
		} catch (Exception e) {
			logger.error(Log.op(op).msg("【富富友存管-协议绑定确认接口】】发送请求未知异常，修改订单为未知").kv("busiId", fuiouUserProtocol.getBusiId()).toString(), e);
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), PayExceptionCode.WARN_UNKNOWN.getDesc());
		}
	}

	/**
	 * 构建协议绑定确认请求参数
	 * 
	 * @author buyi
	 * @date 2018-06-19 19:33:25
	 * @since v2.2.2
	 * @param fuiouUserProtocol
	 * @param config
	 * @return
	 * @throws Exception
	 */
	private Map<String, String> buildProtocolSigning(FuiouUserProtocol fuiouUserProtocol, FuiouDepositConfigExt config, String salt, String validCode)
			throws Exception {
		ProtocolSigningReq req = new ProtocolSigningReq();
		req.setMchntCd(config.getMerchantId());
		req.setMchntTxnSsn(fuiouUserProtocol.getBusiId());
		req.setLoginId(fuiouUserProtocol.getLoginId());
		req.setCardNm(fuiouUserProtocol.getName());
		req.setCardNo(CryptUtil.decrypt(fuiouUserProtocol.getCardNoCip()));
		req.setIdNo(CryptUtil.decrypt(fuiouUserProtocol.getIdNoCip()));
		req.setMobile(fuiouUserProtocol.getPhone());
		req.setIndateNum(String.valueOf(fuiouUserProtocol.getIndateNum()));
		req.setTxnDate(fuiouUserProtocol.getTxnDate());
		req.setSmsCd(validCode);
		req.setSalt(salt);

		// 生成签名串
		String signature = fuiouSignService.getSign(fuiouUserProtocol.getMerchantCode(), req);
		req.setSignature(signature);

		return AliasUtil.coverToMapAll(req);

	}

	/**
	 * 富友存管协议充值
	 * 
	 * @author buyi
	 * @date 2018-06-20 15:09:26
	 * @since v2.2.2
	 * @param fuiouCharge
	 * @return
	 */
	public PayRspCodeVO doProtocolSignRecharge(FuiouCharge fuiouCharge) {
		String op = "FuiouDepositService.doProtocolSignRecharge";
		try {
			FuiouDepositConfigExt config = fuiouSignService.getFuiouDepositConfig(fuiouCharge.getMerchantCode());
			String url = config.getProtocolSignRechargeUrl();

			if (StringUtils.isBlank(url)) {
				logger.error(Log.op(op).msg("【富友存管-协议充值接口】失败，FuiouDepositConfigExt.protocolSignRechargeUrl配置为空，请重新检查merchant_payment.ext配置")
						.kv("FuiouCharge", fuiouCharge).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.ERROR_OPT_FAIL.getCode(), PayExceptionCode.ERROR_OPT_FAIL.getDesc());
			}

			// 封装请求参数
			Map<String, String> reqMap = buildProtocolSignRecharge(fuiouCharge, config);

			logger.info(Log.op(op).msg("【富友存管-协议充值接口】】 http请求").kv("url", url).kv("params", reqMap).kv("busiId", fuiouCharge.getBusiId()).toString());

			String result = HttpUtil.postFormDirect(url, reqMap, null, null, null, null, null, config.getConnectTimeout(), config.getReadTimeout());

			logger.info(Log.op(op).msg("【富友存管-协议充值接口】 http响应").kv("url", url).kv("params", reqMap).kv("result", result)
					.kv("busiId", fuiouCharge.getBusiId()).toString());

			if (StringUtils.isBlank(result)) {
				logger.error(Log.op(op).msg("【富富友存管-协议充值接口】响应参数异常，返回参数为空").kv("url", url).kv("params", reqMap).kv("result", result)
						.kv("busiId", fuiouCharge.getBusiId()).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), PayExceptionCode.WARN_UNKNOWN.getDesc());
			}

			boolean verify = commonVerify(fuiouCharge.getMerchantCode(), result);
			if (!verify) {
				// 返回校验不通过
				logger.error(Log.op(op).msg("【富富友存管-协议充值接口】响应参数异常,验签不通过,返回未知异常").kv("url", url).kv("params", reqMap).kv("result", result)
						.kv("busiId", fuiouCharge.getBusiId()).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), PayExceptionCode.WARN_UNKNOWN.getDesc());
			}

			RootXMLRsp<ProtocalSignSmsPlain> fuiouRsp = (RootXMLRsp<ProtocalSignSmsPlain>) RootXMLRsp.fromXml(result, ProtocalSignSmsPlain.class);

			PayRspCodeVO payRspCodeVO = converToPayRspCode(fuiouRsp.getPlain().getRespCode(), fuiouRsp.getPlain().getRespDesc());
			payRspCodeVO.setObj(fuiouRsp.getPlain());

			return payRspCodeVO;
		} catch (SocketTimeoutException e) {
			logger.error(Log.op(op).msg("【富富友存管-协议充值接口】请求超时").kv("busiId", fuiouCharge.getBusiId()).toString(), e);
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_REQ_TIME_OUT.getCode(), PayExceptionCode.WARN_REQ_TIME_OUT.getDesc());
		} catch (PayException e) {
			return PayRspCodeVO.newInstance(e.getCode(), e.getMessage());
		} catch (Exception e) {
			logger.error(Log.op(op).msg("【富富友存管-协议充值接口】】发送请求未知异常，修改订单为未知").kv("busiId", fuiouCharge.getBusiId()).toString(), e);
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), PayExceptionCode.WARN_UNKNOWN.getDesc());
		}
	}

	/**
	 * 构建协议充值请求参数
	 * 
	 * @author buyi
	 * @date 2018-06-20 15:14:32
	 * @since v2.2.2
	 * @param fuiouCharge
	 * @param config
	 * @return
	 */
	private Map<String, String> buildProtocolSignRecharge(FuiouCharge fuiouCharge, FuiouDepositConfigExt config) {
		ProtocolSignRechargeReq req = new ProtocolSignRechargeReq();
		req.setMchntCd(config.getMerchantId());
		req.setMchntTxnSsn(fuiouCharge.getBusiId());
		req.setLoginId(fuiouCharge.getLoginId());
		req.setAmt(String.valueOf(fuiouCharge.getAmount()));

		// 生成签名串
		String signature = fuiouSignService.getSign(fuiouCharge.getMerchantCode(), req);
		req.setSignature(signature);

		return AliasUtil.coverToMapAll(req);
	}

	/**
	 * 富友存管协议签约查询
	 * 
	 * @author buyi
	 * @date 2018-06-21 10:27:52
	 * @since v2.2.2
	 * @param merchantCode
	 * @param idNo
	 * @param name
	 * @return
	 */
	public PayRspCodeVO queryProtocolSign(String merchantCode, QueryUserProtocolSignReq params) {
		String op = "FuiouDepositService.queryProtocolSign";
		try {
			FuiouDepositConfigExt config = fuiouSignService.getFuiouDepositConfig(merchantCode);
			String url = config.getProtocolSignQueryUrl();

			if (StringUtils.isBlank(url)) {
				logger.error(Log.op(op).msg("【富友存管-协议签约查询接口】失败，FuiouDepositConfigExt.protocolSignQueryUrl配置为空，请重新检查merchant_payment.ext配置")
						.kv("merchantCode", merchantCode).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.ERROR_OPT_FAIL.getCode(), PayExceptionCode.ERROR_OPT_FAIL.getDesc());
			}

			// 封装请求参数
			Map<String, String> reqMap = buildProtocolSignQuery(merchantCode, params, config);

			logger.info(Log.op(op).msg("【富友存管-协议签约查询接口】】 http请求").kv("url", url).kv("params", reqMap).kv("merchantCode", merchantCode)
					.kv("userId", params.getUserId()).toString());

			String result = HttpUtil.postFormDirect(url, reqMap, null, null, null, null, null, config.getConnectTimeout(), config.getReadTimeout());

			logger.info(Log.op(op).msg("【富友存管-协议签约查询接口】 http响应").kv("url", url).kv("params", reqMap).kv("result", result)
					.kv("merchantCode", merchantCode).kv("userId", params.getUserId()).toString());

			if (StringUtils.isBlank(result)) {
				logger.error(Log.op(op).msg("【富富友存管-协议签约查询接口】响应参数异常，返回参数为空").kv("url", url).kv("params", reqMap).kv("result", result)
						.kv("merchantCode", merchantCode).kv("userId", params.getUserId()).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), PayExceptionCode.WARN_UNKNOWN.getDesc());
			}

			boolean verify = commonVerify(merchantCode, result);
			if (!verify) {
				// 返回校验不通过
				logger.error(Log.op(op).msg("【富富友存管-协议签约查询接口】响应参数异常,验签不通过,返回未知异常").kv("url", url).kv("params", reqMap)
						.kv("merchantCode", merchantCode).kv("userId", params.getUserId()).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), PayExceptionCode.WARN_UNKNOWN.getDesc());
			}

			RootXMLRsp<ProtocalSignQueryPlain> fuiouRsp = (RootXMLRsp<ProtocalSignQueryPlain>) RootXMLRsp.fromXml(result,
					ProtocalSignQueryPlain.class);

			PayRspCodeVO payRspCodeVO = converToPayRspCode(fuiouRsp.getPlain().getRespCode(), fuiouRsp.getPlain().getRespDesc());

			// 转换富友存管的响应参数为支付的响应参数
			if (fuiouRsp.getPlain().getList() == null || fuiouRsp.getPlain().getList().isEmpty()) {
				return payRspCodeVO;
			}

			ProtocalSignQueryItemPlain signQueryItemPlain = fuiouRsp.getPlain().getList().get(0);

			QueryUserProtocolSignRsp content = new QueryUserProtocolSignRsp();
			content.setUserId(params.getUserId());
			content.setLoginId(signQueryItemPlain.getLoginId());
			content.setCardNo(signQueryItemPlain.getCardNo());
			content.setIdNo(signQueryItemPlain.getIdNo());
			content.setPhone(signQueryItemPlain.getMobile());
			content.setName(signQueryItemPlain.getUserNm());
			content.setState(FuiouDepositProtocolSignStateEnum.fromValue(signQueryItemPlain.getStatus()).getValue());

			payRspCodeVO.setObj(content);

			return payRspCodeVO;
		} catch (SocketTimeoutException e) {
			logger.error(Log.op(op).msg("【富富友存管-协议签约查询接口】请求超时").kv("merchantCode", merchantCode).kv("userId", params.getUserId()).toString(), e);
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_REQ_TIME_OUT.getCode(), PayExceptionCode.WARN_REQ_TIME_OUT.getDesc());
		} catch (PayException e) {
			return PayRspCodeVO.newInstance(e.getCode(), e.getMessage());
		} catch (Exception e) {
			logger.error(
					Log.op(op).msg("【富富友存管-协议签约查询接口】】发送请求未知异常，修改订单为未知").kv("merchantCode", merchantCode).kv("userId", params.getUserId()).toString(),
					e);
			return PayRspCodeVO.newInstance(PayExceptionCode.WARN_UNKNOWN.getCode(), PayExceptionCode.WARN_UNKNOWN.getDesc());
		}
	}

	/**
	 * 构建协议签约查询请求参数
	 * 
	 * @author buyi
	 * @date 2018-06-21 14:58:06
	 * @since v2.2.2
	 * @param merchantCode
	 * @param params
	 * @param config
	 * @return
	 */
	private Map<String, String> buildProtocolSignQuery(String merchantCode, QueryUserProtocolSignReq params, FuiouDepositConfigExt config) {
		ProtocolSignQueryReq req = new ProtocolSignQueryReq();
		req.setMchntCd(config.getMerchantId());
		req.setQueryValue(params.getIdNo());
		// req.setStartTime(DateUtil.date2Str(params.getSignDate(), DATE_FORMAT));
		// req.setEndTime(DateUtil.date2Str(params.getSignDate(), DATE_FORMAT));
		String startTime = fuiouDepositConstant.getQueryProtocolStartDate();
		String endTime = fuiouDepositConstant.getQueryProtocolEndDate();

		if (StringUtils.isNotBlank(params.getSignDate())) {
			// 如果传参签约时间不为空，则根据签约时间计算查询起止时间

			Date signDate = DateUtil.str2DateTime(params.getSignDate());

			// 签约查询开始时间扩大为前七天，防止跨天的场景
			startTime = DateUtil.date2Str(fuiouDepositConstant.getTradeQueryStartDay(signDate), DATE_FORMAT);
			// 签约查询开始时间扩大为后七天，防止跨天的场景
			endTime = DateUtil.date2Str(fuiouDepositConstant.getTradeQueryEndDay(signDate), DATE_FORMAT);
		}

		req.setStartTime(startTime);
		req.setEndTime(endTime);

		// 生成签名串
		String signature = fuiouSignService.getSign(merchantCode, req);
		req.setSignature(signature);

		return AliasUtil.coverToMapAll(req);
	}

}
